package fastcgi

import (
	"net"
	"net/http"
	"path"
	"path/filepath"
	"regexp"
	"strings"
)

type FastCGIParam map[string]string

func NewFastCGIParam() FastCGIParam {
	return FastCGIParam{
		"DOCUMENT_ROOT":   "",
		"SCRIPT_FILENAME": "",
		"SCRIPT_NAME":     "",

		"REQUEST_SCHEME":    "",
		"SERVER_NAME":       "",
		"SERVER_PORT":       "",
		"REDIRECT_STATUS":   "200",
		"HTTP_HOST  ":       "",
		"SERVER_SOFTWARE":   "gateway/1.0.0",
		"REMOTE_ADDR":       "",
		"SERVER_PROTOCOL ":  "",
		"GATEWAY_INTERFACE": "CGI/1.1",

		"PATH_INFO": "",

		"REQUEST_METHOD": "",
		"REQUEST_URI  ":  "",
		"QUERY_STRING ":  "",

		"DOCUMENT_URI": "",
		"PHP_SELF":     "",

		//只有自定义的需要加上HTTP_xxx_XXX_XXX
		//for header, values := range r.Header {
		//	HTTP_"+strings.Replace(strings.ToUpper(header), "-", "_", -1)] = values[0]
		//}

		"CONTENT_LENGTH": "",
		"CONTENT_TYPE ":  "",
	}
}

type ParamHandler func(params FastCGIParam, req *http.Request)

type ParamMiddleware func(handler ParamHandler) ParamHandler

func Group(middleware ...ParamMiddleware) ParamMiddleware {
	if len(middleware) == 0 {
		return nil
	}

	return func(inner ParamHandler) (out ParamHandler) {
		out = inner
		for i := len(middleware) - 1; i >= 0; i-- {
			out = middleware[i](out)
		}
		return
	}
}

// MapHeader implement Middleware to map header field HTTP_*
//
// It is a convention to map header field SomeRandomField to
// HTTP_SOME_RANDOM_FIELD. For example, if a header field "X-Hello-World" is in
// the header, it will be mapped as "HTTP_X_HELLO_WORLD" in the fastcgi parameter
// field.
//
// Note: HTTP_CONTENT_TYPE and HTTP_CONTENT_LENGTH cannot be overridden.
func MapHeader(handler ParamHandler) ParamHandler {
	return func(params FastCGIParam, req *http.Request) {

		for key, _ := range req.Header {
			value := req.Header.Get(key)
			if value != "" {
				key = strings.ToUpper(key)
				key = strings.Replace(key, "-", "_", -1)
				key = "HTTP_" + key
				params[key] = value
			}

		}

		if remoteAllAddr, ok := params["HTTP_REMOTE_ADDR"]; ok {
			remoteAddr, remotePort, _ := net.SplitHostPort(remoteAllAddr)
			params["REMOTE_ADDR"] = remoteAddr
			params["REMOTE_PORT"] = remotePort
		}

		handler(params, req)
	}

}

// BasicParamsMap implements Middleware. It maps basic parameters to the
// params.
//
// Parameters included:
//
//	CONTENT_TYPE
//	CONTENT_LENGTH
//	HTTPS
//	GATEWAY_INTERFACE
//	REMOTE_ADDR
//	REMOTE_PORT
//	SERVER_PORT
//	SERVER_NAME
//	SERVER_PROTOCOL
//	SERVER_SOFTWARE
//	REDIRECT_STATUS
//	REQUEST_METHOD
//	REQUEST_SCHEME
//	REQUEST_URI
//	QUERY_STRING
func BasicParamsMap(handler ParamHandler) ParamHandler {
	return func(params FastCGIParam, req *http.Request) {
		isHTTPS := len(req.URL.Scheme) == 5 && req.URL.Scheme[4] == 's'
		if isHTTPS {
			params["HTTPS"] = "on"
		}

		host, serverPort, err := net.SplitHostPort(req.Host)
		if err != nil {
			host = req.Host
			if isHTTPS {
				serverPort = "443"
			} else {
				serverPort = "80"
			}
		}

		// the basic information here
		params["CONTENT_TYPE"] = params["HTTP_Content_Type"]
		params["CONTENT_LENGTH"] = params["HTTP_Content_Length"]
		params["GATEWAY_INTERFACE"] = "CGI/1.1"

		params["SERVER_PORT"] = serverPort
		params["SERVER_NAME"] = host
		params["SERVER_PROTOCOL"] = "http/1.1"
		params["SERVER_SOFTWARE"] = "gitee.com/tiger000/taishan_web/1.0.0"
		params["REDIRECT_STATUS"] = "200"
		params["REQUEST_SCHEME"] = req.URL.Scheme
		params["REQUEST_METHOD"] = req.Method
		params["REQUEST_URI"] = req.RequestURI
		params["QUERY_STRING"] = req.URL.RawQuery

		handler(params, req)
	}
}

// FileSystemRouter helps to produce Middleware implementation for
// mapping path related fastcgi parameters. See method Router for usage.
type FileSystemRouter struct {

	// DocRoot stores the ordinary Apache DocumentRoot parameter
	DocRoot string

	// Exts stores accepted extensions
	Exts []string

	// DirIndex stores ordinary Apache DirectoryIndex parameter
	// for to identify file to show in directory
	DirIndex []string
}

// Router returns a Middleware that prepare session parameters that are
// path related. With information provided in the FileSystemRouter, it will
// route request to script files which path matches the http request path.
//
// i.e. classic PHP hosting environment like Apache + mod_php
//
// Parameters included:
//
//	PATH_INFO
//	PATH_TRANSLATED
//	SCRIPT_NAME
//	SCRIPT_FILENAME
//	DOCUMENT_URI
//	DOCUMENT_ROOT
func (fs *FileSystemRouter) Router() ParamMiddleware {
	pathInfoRegex := regexp.MustCompile(`^(.+\.php)(/?.+)$`)
	docRoot := filepath.Join(fs.DocRoot) // converts to absolute path
	return func(inner ParamHandler) ParamHandler {
		return func(params FastCGIParam, req *http.Request) {
			// define some required cgi parameters
			// with the given http request

			fastcgiScriptName := req.URL.Path
			if path.Ext(fastcgiScriptName) != ".php" {
				fastcgiScriptName = "/index.php" + fastcgiScriptName
			}

			var fastcgiPathInfo string
			if matches := pathInfoRegex.FindStringSubmatch(fastcgiScriptName); len(matches) > 0 {
				fastcgiScriptName, fastcgiPathInfo = matches[1], matches[2]
			}

			// If accessing a directory, try accessing document index file
			if strings.HasSuffix(fastcgiScriptName, "/") {
				fastcgiScriptName = path.Join(fastcgiScriptName, "index.php")
			}

			params["PATH_INFO"] = fastcgiPathInfo
			params["PATH_TRANSLATED"] = filepath.Join(docRoot, fastcgiPathInfo)
			params["SCRIPT_NAME"] = fastcgiScriptName
			params["SCRIPT_FILENAME"] = filepath.Join(docRoot, fastcgiScriptName)
			params["DOCUMENT_URI"] = req.URL.Path
			params["DOCUMENT_ROOT"] = docRoot

			inner(params, req)
		}
	}
}
